#include "simulation.h"

Simulation::Simulation(int argc, char **argv) {

	avg_service_time = 0;
	avg_waiting_time = 0;
	avg_system_time = 0;	

	kSeed_arrival = time(NULL);				//seed for the generating the interarrival times
	kSeed_service = time(NULL) + 10467;		//seed for the generating the service times

	//command line inputs and validations
	lambda = atof(argv[1]);		//0 < lambda < 1
	if (lambda > 1) {
		cout << "Invalid value of lambda. lambda must be in (0, 1)" << endl;
		return;
	}
	mu = 1;						//mu = 1 always
	kcpu = atoi(argv[2]);
	kio = atoi(argv[3]);

	if (kcpu <= 0) {
		cout << "Invalid value of K_CPU" << endl;
		return;
	}
	if (l == 1 && kio <= 0) {
		cout << " Invalid value of K_IO" << endl;
		return;
	}
	c = atoi(argv[4]);
	if (c <= 0) {
		cout << "Invalid value of C. C must be greater than 0" << endl;
		return;
	}
	l = atoi(argv[5]);
	if (! (l == 0 || l == 1)) {
		cout << "Invalid value of L" << endl;
		exit(0);
	}
	m = atoi(argv[6]);
	if (m < 0 || m > 5) {
		cout << "Invalid value of M" << endl;
		exit(0);
	}

	Ta = 200.0;
	Ts = 100.0;
	te = 1<<30;
	n = 0;
	t1 = expntl(1/lambda, kSeed_arrival);
	t2 = te;
	sim_time = 0.0;	

	serve_count = 0;
	master_clock = 0;

	arrival_count = 0;
	departure_count = 0;
	total_arrival_count = 0;
	total_departure_count = 0;

	//initiate the arrival of the first customer, generate the corresponding event,
	//add it to the event list and add the customer to the customer_list
	//set simulation time
	Ts = expntl(1/mu, kSeed_service);
	Customer *customer = new Customer(total_arrival_count++, t1, Ts, 0.0, 0.0, false, false);
	EventNode *next_event = new EventNode('A', customer, t1);
	event_list_add(next_event);
	sim_time = t1;
	customer_list.push_back(customer);
}

//function to generate the random number in the range (0, 1), given a seed
double Simulation::ran0(long *idnum) {
	long k;      
	double ans;

	*idnum ^= MASK;
	k = (*idnum)/IQ;
	*idnum = IA * (*idnum - k * IQ) - IR * k;
	if (*idnum < 0) {
		*idnum += IM;
	}
	ans = AM * (*idnum);
	*idnum ^= MASK;
	return ans;
}

//function to generate an exponentially distributed random number with mean 1
double Simulation::expdev(long *idnum) {
	double dummy;
	do {
		dummy = ran0(idnum);
	} while (dummy == 0.0);
	return -log(dummy);
}

//function to generate an exponentially distributed random number with mean "mean"
double Simulation::expntl(double mean, long &seed) {
	return 1.0 * mean * Simulation::expdev(&seed);
}

Simulation::EventNode *Simulation::find_event_for_customer(Customer *customer, char eventType) {
	for (uint32_t index = 0; index < event_list.size(); ++index) {
		if (event_list[index]->type_ == eventType &&
			event_list[index]->customer_ == customer) {
			return event_list[index];	
		}
	}
	return NULL;
}

bool Simulation::event_list_empty() {
	return event_list.empty();
}

//returns the current size of the event list
uint32_t Simulation::event_list_size() {
	return event_list.size();
}

//returns the first element (i.e. the next event) from the event list
Simulation::EventNode *Simulation::event_list_front() {
	if (! event_list_empty()) {
		return event_list[0];
	}
	return NULL;	
}

//adds a new element (i.e. a new event) to the event list, ordered by the time of the event
void Simulation::event_list_add(EventNode *eventNode) {
	if (event_list_empty()) {
		event_list.push_back(eventNode);
		return;
	} else if (event_list_size() == 1) {
		if (event_list[0]->time_ <= eventNode->time_) {
			event_list.push_back(eventNode);
		} else {
			event_list.insert(event_list.begin(), eventNode);
		} 
	} else {
		uint32_t count = 0;
		while (event_list[count]->time_ <= eventNode->time_) { 
			count++;
			if (count == event_list_size()) {
				break;
			}
		}
		if (count < event_list_size()) {
			event_list.insert(event_list.begin() + count, eventNode);
		} else {
			event_list.push_back(eventNode);
		}
	}
	//event_list_print();
}

Simulation::EventNode *Simulation::event_list_remove(EventNode *eventNode) {
	EventNode *event = NULL;
	for (uint32_t index = 0; index < event_list.size(); ++index) {
		if (event_list[index]->type_ == eventNode->type_ &&
			event_list[index]->customer_ == eventNode->customer_ &&
			event_list[index]->time_ == eventNode->time_) {
			event = event_list[index];
			event_list.erase(event_list.begin() + index);
			break;
		}
	}
	return event;
}

//removes an element (i.e. the next event) from the event list
Simulation::EventNode *Simulation::event_list_remove_front() {
	if (event_list_empty()) {
		return NULL;
	}
	EventNode *event = event_list_front();
	event_list.erase(event_list.begin());
	return event;
}

//prints the contents of the event list
void Simulation::event_list_print() {
	cout << "\nSTART\n";
	for (uint32_t index = 0; index < event_list.size(); ++index) {
		event_list[index]->print();
	}
	cout << "\nEND\n";
}

void Simulation::calculate_statistics() {
	//double avg_service_time = 0, avg_waiting_time = 0, avg_system_time = 0;
	uint32_t customers_lost = 0, customers_serviced = 0;
	//cout << "customer list size = " << customer_list.size() << endl;
	for (uint32_t index = 0; index < customer_list.size(); ++index) {
		customer_list[index]->waiting_time_ = customer_list[index]->departure_time_ - 
										      customer_list[index]->service_time_ -
											  customer_list[index]->arrival_time_;
	//	if (customer_list[index]->serviced_ && customer_list[index]->departure_number_ > 1000) {
		if (customer_list[index]->departure_number_ > 1000) {		
			customers_serviced++;
            customer_list[index]->slowdown_ = 1.0 * customer_list[index]->waiting_time_ / customer_list[index]->service_time_;
			avg_service_time += customer_list[index]->service_time_;
			avg_waiting_time += customer_list[index]->waiting_time_;
			avg_system_time += (customer_list[index]->departure_time_ -
								customer_list[index]->arrival_time_);
            //cout << customer_list[index]->service_time_ << " " << customer_list[index]->slowdown_ << endl;
			//customer_list[index]->print();
		} 
		if (customer_list[index]->lost_) {
			customers_lost++;
		}
		//customer_list[index]->print();
	}
	avg_service_time /= (c - 1000);
	avg_waiting_time /= (c - 1000);
	avg_system_time /= (c - 1000);
	clr = 1.0 * (total_arrival_count - c) / (total_arrival_count - 10000);
	//avg_service_time /= customers_serviced;
	//avg_waiting_time /= customers_serviced;
}

void Simulation::customer_diagnostics(uint32_t index) {
	if (index == 0 || index > customer_list.size()) {
		cout << "simtime in arrival = " << sim_time << endl;
		cout << "Specified customer ID out of range" << endl;
		return;
	}
	cout << "Customer " << index << endl;
	if (customer_list[index - 1]->serviced_) {
		cout << "Arrival time = " << customer_list[index - 1]->arrival_time_ << endl;
		cout << "Service time = " << customer_list[index - 1]->service_time_ << endl;
		cout << "Departure time = " << customer_list[index - 1]->departure_time_ << endl;
		cout << "Number of customers in the system after the departure = " << customer_list[index - 1]->customers_after_ << endl;
	} else {
		cout << "Not serviced. Setting departure time = arrival time" << endl;
		customer_list[index - 1]->departure_time_ = customer_list[index - 1]->arrival_time_;
	}
}

void Simulation::run() {
	while (serve_count < c) {
		//this->print_queues();
		//event_list_print();
		EventNode *event = event_list_remove_front();
		if (event != NULL) {
			if (event->type_ == 'A') {
				this->process_arrival(event);
			} else if (event->type_ == 'D') {
				this->process_departure(event);
			} else if (event->type_ == 'L') {
				this->process_lost(event);
			}
		} else {
			Ta = expntl(1/lambda, kSeed_arrival);
			Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, 0.0, 0.0, 0.0, false, false);
			EventNode *next_event = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_event);
			customer_list.push_back(customer);
		}
		//cout << "serve count = " << serve_count << endl;
	}
	total_arrival_count--;
}
